﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Mogre;

namespace WastedMechanics
{
    public class GuiControl
    {
        public string Name;
        public object UserData;
        public OverlayContainer Panel;        
        public bool IsDraggable;        
        protected List<GuiControl> Children;
        public GuiControl Parent;
        public bool IsDestroyed;
        public bool IsDetached;
        private bool IsUpdated;
        protected bool IsMouseIn;
        public bool StopsKeyboardInput;

        public event Predicate<GuiControl> OnClick;
        public event Predicate<GuiControl> OnMousePress;
        public event Predicate<GuiControl> OnMouseRightPress;
        public event Func<GuiControl, GuiControl, bool> OnDropAccept;
        public event Action<GuiControl, GuiControl> OnDrop;
        public event Action<GuiControl> OnDragBegin;
        public event Action<GuiControl> OnMouseEntered;
        public event Action<GuiControl> OnMouseLeft;
        public event Action<GuiControl,MOIS.KeyCode> OnKeyTyped;

        public GuiControl(string name)
        {
            Name = name;
            Children = new List<GuiControl>();

            Panel = OverlayManager.Singleton.CreateOverlayElement("Panel", Name) as OverlayContainer;
            Panel.MetricsMode = GuiMetricsMode.GMM_RELATIVE;
            Panel.SetDimensions(1, 1);
            Panel.SetPosition(0, 0);
        }

        public void Destroy()
        {
            // Children can be removed from parent's list
            // only when we aren't iterating through it (updating other children)
            if (Parent != null)
                if (Parent.IsUpdated == false)
                    Parent.Children.Remove(this);
            // if control can't be removed from parent's child list
            // it is marked as destroyed, and it'll be removed at first 
            // CleanChildren() call
            IsDestroyed = true;
            DestroyInternals();
        }

        public void Detach()
        {
            if (Parent != null)
            {
                if (Parent.IsUpdated == false)
                    Parent.Children.Remove(this);
                IsDetached = true;
                Parent.Panel.RemoveChild(Panel.Name);
                Parent = null;
            }
        }

        public void AttachTo(GuiControl parent)
        {            
            if (Parent != null)
                throw new Exception("Control " + Name + " is already attached to " + Parent.Name);
            IsDetached = false;
            parent.Panel.AddChild(Panel);
            parent.Children.Add(this);
            Parent = parent;
        }

        public void DestroyAllChildren()
        {
            BeginChildrenUpdate();
            Children.ForEach(child => child.Destroy());
            EndChildrenUpdate();
        }
                  
        protected virtual void DestroyInternals()
        {
            foreach (GuiControl child in Children)
                child.DestroyInternals();

            if (Parent != null)
                Parent.Panel.RemoveChild(Panel.Name);    
            OverlayManager.Singleton.DestroyOverlayElement(Panel);
        }

        protected virtual void UpdateInternals()
        {
        }

        public void Update()
        {
            BeginChildrenUpdate();
            UpdateInternals();            
            Children.ForEach(control => control.Update());
            EndChildrenUpdate();
        }
               
        public bool GiveInput()
        {            
            if (IsVisible)
            {                
                BeginChildrenUpdate();
                
                bool hasInput = true;
                // if any child will take input, other won't receive it.
                foreach (var child in Children)                
                    if (child.GiveInput() == false)
                    {
                        hasInput = false;
                        break;
                    }                
                
                EndChildrenUpdate();

                if (hasInput)
                    return ProcessInput();
                else
                    return false;
            }
            // invisible control cannot take input...
            return true;
        }

        public GuiControl DropAccept(GuiControl control)
        {
            BeginChildrenUpdate();
            GuiControl dropDest = null;
            foreach (var child in Children)
            {
                GuiControl result = child.DropAccept(control);
                if (result != null)
                {
                    dropDest = result;
                    break;
                }                
            }

            if (dropDest == null)
                if (OnDropAccept != null && ContainsPoint(Engine.MouseInput.Position))
                    if (OnDropAccept(this, control))
                        dropDest = this;

            EndChildrenUpdate();
            return dropDest;
        }

        public void DropReceive(GuiControl control)
        {
            if (OnDrop != null)            
                OnDrop(this, control);
            else
                control.AttachTo(this);
            Update();
        }

        public bool DragBegin()
        {
            if (OnDragBegin != null)
            {
                OnDragBegin(this);
                return true;
            }
            return false;
        }
        
        // if return false, input will not be processed by controls parent
        protected virtual bool ProcessInput()
        {
            if (ContainsPoint(Engine.MouseInput.Position))
            {
                if (IsMouseIn == false)
                {
                    IsMouseIn = true;
                    if (OnMouseEntered != null)
                        OnMouseEntered(this);
                }

                if (IsDraggable)
                    if (CheckDrag() == false)
                        return false;                
                if (Engine.MouseInput.ButtonPressed(MOIS.MouseButtonID.MB_Left))
                {
                    if (OnMousePress != null)
                        return OnMousePress(this);
                    Engine.GuiManager.FocusedControl = this;
                }

                if (Engine.MouseInput.ButtonReleased(MOIS.MouseButtonID.MB_Left))
                    if (OnClick != null)
                        return OnClick(this);

                if (Engine.MouseInput.ButtonPressed(MOIS.MouseButtonID.MB_Right))
                    if (OnMouseRightPress != null)
                        return OnMouseRightPress(this);                
            }
            else
            {
                if (IsMouseIn == true)
                {
                    IsMouseIn = false;
                    if (OnMouseLeft != null)
                        OnMouseLeft(this);
                }
            }
            return true;
        }

        public void KeyTyped(MOIS.KeyCode key)
        {
            if (OnKeyTyped != null)
                OnKeyTyped(this, key);
            if (!StopsKeyboardInput)
                if (Parent != null)
                    Parent.KeyTyped(key);
        }

        private bool CheckDrag()
        {
            if (Engine.MouseInput.ButtonPressed(MOIS.MouseButtonID.MB_Left))
            {
                Engine.GuiManager.DragOffset.x = Engine.MouseInput.Position.x - DerivedLeft;
                Engine.GuiManager.DragOffset.y = Engine.MouseInput.Position.y - DerivedTop;
                Engine.GuiManager.DragControl = this;
                Engine.GuiManager.FocusedControl = this;
                return false;
            }
            return true;
        }

        public bool ContainsPoint(Vector2 pt)
        {
            return Helper.PointInRect(DerivedLeft, DerivedTop, Width, Height, pt);
        }
        
        protected void CleanChildren()
        {
            Children.RemoveAll(control => control.IsDestroyed || control.IsDetached);
        }

        protected void BeginChildrenUpdate()
        {
            if (IsUpdated)
                throw new Exception(Name + " is already being updated!");
            IsUpdated = true;
        }

        protected void EndChildrenUpdate()
        {
            IsUpdated = false;
            CleanChildren();
        }

        public string MaterialName
        {
            get
            {
                return Panel.MaterialName;
            }
            set
            {
                Panel.MaterialName = value;                
            }
        }

        public float Left
        {
            get
            {
                return Panel.Left;
            }
            set
            {
                Panel.Left = value;
            }
        }

        public float Top
        {
            get
            {
                return Panel.Top;
            }
            set
            {
                Panel.Top = value;
            }
        }

        public float Height
        {
            get
            {
                return Panel.Height;
            }
            set
            {
                Panel.Height = value;
            }
        }

        public float Width
        {
            get
            {
                return Panel.Width;
            }
            set
            {
                Panel.Width = value;                
            }
        }

        public float DerivedLeft
        {
            get
            {
                return Panel._getDerivedLeft();
            }
        }

        public float DerivedTop
        {
            get
            {
                return Panel._getDerivedTop();
            }
        }

        public bool IsVisible
        {
            get
            {
                if (IsDestroyed)
                    return false;
                if (Parent != null)
                    return Parent.IsVisible && Panel.IsVisible;
                else 
                    return Panel.IsVisible;
            }
            set
            {
                if (value)
                    Panel.Show();
                else
                    Panel.Hide();
            }
        }

        public void Show()
        {
            IsVisible = true;
        }
        public void Hide()
        {
            IsVisible = false;
        }

        public static bool StopInput(GuiControl sender)
        {
            return false;
        }
    }
}
